﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using RemoteCode.Attributes;
using SecureConnection.Utils;


namespace RemoteCode
{
    internal class InvokeHelper
    {
        private static object syncobject = new object();

        public static ConstructorInfo FindConstructor(Type classType, object[] parameters, bool secure = true)
        {
            ConstructorInfo ctorinfo = null;
            try
            {
                ctorinfo = classType.GetConstructor(TypeHelper.ObjectArrayToTypeArray(parameters));
                if (ctorinfo == null)
                    throw new Exception("Contructor not found!");
                if (secure && ctorinfo.GetCustomAttributes(typeof(RemoteExecutable), true).Length == 0)
                    throw new Exception("RemoteExecutable attribute not found!");
                return ctorinfo;
            }
            catch (Exception ex)
            {
                string Types = "";
                try
                {
                    foreach (Type t in TypeHelper.ObjectArrayToTypeArray(parameters))
                        Types += t.Name + " ";
                }
                catch { }
                throw new Exception("Parameter Types: " + Types, ex);
            }
        }

        public static Result InvokeStatic(ref ByteArrayReader br, Nexum host)
        {
            string className = null;
            string methodName = null;
            object[] parameters = null;
            bool pointer = false;

            try
            {
                pointer = br.ReadByte() == 1;
                className = br.ReadString();

                methodName = br.ReadString();
                parameters = SerializationHelper.DeserializeArray(ref br);
            }
            catch (Exception ex) { return new Result(true, ex, NexumError.PacketParsingError, null); }

            Type classType = null;
            lock (host.ClassesAllowedToShare)
            {
                if (!host.ClassesAllowedToShare.ContainsKey(className))
                    return new Result(true, null, NexumError.NotShared, null);

                classType = host.ClassesAllowedToShare[className];
            }

            return InvokeMethod(classType, methodName, parameters, null, pointer, host);
        }

        public static Result InvokeMethod(Type classType, string methodName, object[] parameters, object classObject, bool pointer, Nexum host)
        {
            object retval = null;
            try
            {
                MethodInfo methodinfo = null;
                try
                {
                    MethodInfo[] methods = classType.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);

                    bool ok;
                    ParameterInfo[] paraInfos;
                    for (int i = 0; i < methods.Length; i++)
                    {
                        if (methods[i].Name == methodName)
                        {
                            ok = true;
                            paraInfos = methods[i].GetParameters();

                            if (paraInfos == null)
                            {
                                if (parameters.Length != 0)
                                    ok = false;
                            }
                            else
                                if (parameters.Length != paraInfos.Length)
                                    ok = false;
                                else
                                    for (int j = 0; j < paraInfos.Length; j++)
                                        if (paraInfos[j].ParameterType != parameters[j].GetType())
                                        {
                                            ok = false;
                                            break;
                                        }

                            if (ok)
                            {
                                methodinfo = methods[i];
                                break;
                            }
                        }
                    }
                    if (methodinfo == null)
                        throw new Exception("Method not found! " + methodName);
                    if (methodinfo.GetCustomAttributes(typeof(RemoteExecutable), true).Length == 0)
                        throw new Exception("RemoteExecutable attribute not found!");
                    if (pointer && (!methodinfo.ReturnType.IsClass || methodinfo.ReturnType.GetCustomAttributes(typeof(Shareable), true).Length == 0 || !host.IsClassShared(methodinfo.ReturnType)))
                        throw new Exception("Error, cannot point to return type!");
                }
                catch (Exception ex)
                {
                    string Types = "";
                    try
                    {
                        foreach (Type t in TypeHelper.ObjectArrayToTypeArray(parameters))
                            Types += t.Name + " ";
                    }
                    catch { }
                    return new Result(true, new Exception("Parameter Types: " + Types, ex), NexumError.MethodNotFound, null);
                }

                try
                {
                    ExecDelegate del = new ExecDelegate((object classObj, object[] param, MethodInfo method) => { return method.Invoke(classObj, param); });
                    IAsyncResult ar = del.BeginInvoke(classObject, parameters, methodinfo, null, null);
                    if (!ar.AsyncWaitHandle.WaitOne(host.FunctionTimeout))
                        return new Result(false, null, NexumError.Timeout, null);
                    retval = del.EndInvoke(ar);
                }
                catch (Exception ex)
                {
                    return new Result(true, ex, NexumError.InvokeError, null);
                }

                if (pointer)
                {
                    lock (host.LocalClasses)
                    {
                        int id = host.generator.Generate(host.LocalClasses.Keys);

                        LocalClass l = new LocalClass();
                        l.classObject = retval;
                        l.id = id;
                        l.type = retval.GetType();

                        host.LocalClasses.Add(id, l);
                        return new Result(false, null, NexumError.Success, id);
                    }
                }
                return new Result(false, null, NexumError.Success, retval);
            }
            finally
            {
                for (int i = 0; i < parameters.Length; i++)
                {
                    parameters[i] = null;
                }
                parameters = null;
                retval = null;
            }
        }
        private delegate object ExecDelegate(object classObject, object[] parameters, MethodInfo method);
    }
}
